package com.crazymakercircle.mutithread.basic.create;

import com.crazymakercircle.util.Print;

import static com.crazymakercircle.util.ThreadUtil.getCurThreadName;

/**
 * Created by 尼恩@疯狂创客圈.
 *
 * 通过实现Runnable接口的方式创建线程目标类有以下缺点：
 * （1）所创建的类并不是线程类，而是线程的target执行目标类，需要将其实例作为参数传入线程类的构造器，才能创建真正的线程。
 * （2）如果访问当前线程的属性（甚至控制当前线程），不能直接访问Thread的实例方法，必须通过Thread.currentThread()获取当前线程实例，
 * 才能访问和控制当前线程。
 *
 * 通过实现Runnable接口的方式创建线程目标类有以下优点：
 * （1）可以避免由于Java单继承带来的局限性。如果异步逻辑所在类已经继承了一个基类，
 * 就没有办法再继承Thread类。比如，当一个Dog类继承了Pet类，再要继承Thread类就不行了。所以在已经存在继承关系的情况下，
 * 只能使用实现Runnable接口的方式。
 * （2）逻辑和数据更好分离。通过实现Runnable接口的方法创建多线程更加适合同一个资源被多段业务逻辑并行处理的场景。
 * 在同一个资源被多个线程逻辑异步、并行处理的场景中，通过实现Runnable接口的方式设计多个target执行目标类可以更加方便、
 * 清晰地将执行逻辑和数据存储分离，更好地体现了面向对象的设计思想。
 */
public class CreateDemo2
{
    public static final int MAX_TURN = 5;


    static int threadNo = 1;

    /**
     * 在Thread类的run()方法中，如果target（执行目标）不为空，就执行target属性的run()方法。
     * 而target属性是Thread类的一个实例属性，并且target属性的类型为Runnable。
     */
    static class RunTarget implements Runnable  //① 实现Runnable接口
    {
        @Override
        public void run()  //② 在这些写业务逻辑
        {
            for (int j = 1; j < MAX_TURN; j++)
            {
                /**
                 * 【注意】值得注意的是，run()方法实现版本中在获取当前线程的名称时，
                 * 所用的方法是在外部类ThreadUtil中定义的getCurThreadName()静态方法，
                 * 而不是Thread类的getName()实例方法。
                 *
                 * 原因是：这个RunTarget内部类和Thread类不再是继承关系，
                 * 无法直接调用Thread类的任何实例方法。
                 * 通过实现Runnable接口的方式创建的执行目标类，如果需要访问线程的任何属性和方法，
                 * 必须通过Thread.currentThread()获取当前的线程对象，通过当前线程对象间接访问。
                 *
                 * 通过继承Thread类的方式创建的线程类，可以在子类中直接调用Thread父类的方法访问当前线程的名称、状态等信息。
                 */
                Print.cfo(getCurThreadName() + ", 轮次：" + j);
            }

            Print.cfo(getCurThreadName() + " 运行结束.");
        }
    }

    public static void main(String args[]) throws InterruptedException
    {
        /**
         * 通过实现runnable接口创建线程的步骤：
         * （1）定义一个新类实现Runnable接口。
         * （2）实现Runnable接口中的run()抽象方法，将线程代码逻辑存放在该run()实现版本中。
         * （3）通过Thread类创建线程对象，将Runnable实例作为实际参数传递给Thread类的构造器，
         * 由Thread构造器将该Runnable实例赋值给自己的target执行目标属性。
         * （4）调用Thread实例的start()方法启动线程。
         * （5）线程启动之后，线程的run()方法将被JVM执行，该run()方法将调用target属性的run()方法，
         * 从而完成Runnable实现类中业务代码逻辑的并发执行。
         */

        Thread thread = null;

        //方法2.1：使用实现Runnable的实现类创建和启动线程

        for (int i = 0; i < 2; i++)
        {
            Runnable target = new RunTarget();
            thread = new Thread(target, "RunnableThread" + threadNo++);
            thread.start();
        }

        //方法2.2：使用实现Runnable的匿名类创建和启动线程

        for (int i = 0; i < 2; i++)
        {
            //思考1：通过匿名类创建和静态类创建的区别？
            // 1. 静态类在方法执行结束后就销毁，静态类创建在堆区，要在程序执行结束才销毁；
            // 2. 匿名类创建在栈中，如果代码死循环，则会导致栈溢出；静态类分配在堆区，异常会堆溢出。
            //思考2：如何选择这两种创建方式？
            thread = new Thread(new Runnable()
            {
                @Override
                public void run()
                {
                    for (int j = 1; j < MAX_TURN; j++)
                    {
                        Print.cfo(getCurThreadName() + ", 轮次：" + j);
                    }
                    Print.cfo(getCurThreadName() + " 运行结束.");
                }
            }, "RunnableThread" + threadNo++);
            thread.start();
        }

        //方法2.3：使用实现Lambda表达式创建和启动线程
        for (int i = 0; i < 2; i++)
        {
            thread = new Thread(() ->
            {
                for (int j = 1; j < MAX_TURN; j++)
                {
                    Print.cfo(getCurThreadName() + ", 轮次：" + j);
                }
                Print.cfo(getCurThreadName() + " 运行结束.");
            }, "RunnableThread" + threadNo++);
            thread.start();
        }
        Print.cfo(getCurThreadName() + " 运行结束.");
    }
}